/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.windows;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.*;
import org.openide.loaders.DataObject;
import org.openide.util.io.NbMarshalledObject;
import org.openide.util.NbBundle;
/** A top component which may be cloned.
* Typically cloning is harmless, i.e. the data contents (if any)
* of the component are the same, and the new component is merely
* a different presentation.
* Also, a list of all cloned components is kept.
*
* @author Jaroslav Tulach
*/
public abstract class CloneableTopComponent extends TopComponent
implements java.io.Externalizable, TopComponent.Cloneable {
/** generated Serialized Version UID */
static final long serialVersionUID = 4893753008783256289L;
/** reference with list of components */
private Ref ref;
/** Create a cloneable top component.
*/
public CloneableTopComponent () {
}
/** Create a cloneable top component associated with a data object.
* @param obj the data object
* @see TopComponent#TopComponent(DataObject)
*/
public CloneableTopComponent (DataObject obj) {
super (obj);
}
/** Clone the top component and register the clone.
* @return the new component
*/
public final Object clone () {
return cloneComponent ();
}
/** Clone the top component and register the clone.
* Simply calls createClonedObject () and registers the component to
* Ref.
*
* @return the new cloneable top component
*/
public final CloneableTopComponent cloneTopComponent() {
CloneableTopComponent top = createClonedObject ();
// register the component if it has not been registered before
top.setReference (getReference ());
return top;
}
/** Clone the top component and register the clone.
* @return the new component
*/
public final TopComponent cloneComponent() {
return cloneTopComponent ();
}
/** Called from {@link #clone} to actually create a new component from this one.
* The default implementation only clones the object by calling {@link Object#clone}.
* Subclasses may leave this as is, assuming they have no special needs for the cloned
* data besides copying it from one object to the other. If they do, the superclass
* method should be called, and the returned object modified appropriately.
* @return a copy of this object
*/
protected CloneableTopComponent createClonedObject () {
try {
// clones the component using serialization
NbMarshalledObject o = new NbMarshalledObject (this);
CloneableTopComponent top = (CloneableTopComponent)o.get ();
return top;
} catch (IOException ex) {
ex.printStackTrace();
throw new InternalError ();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
throw new InternalError ();
}
}
/** Get a list of all components which are clone-sisters of this one.
*
* @return the clone registry for this component's group
*/
public synchronized final Ref getReference () {
if (ref == null) {
ref = new Ref (this);
}
return ref;
}
/** Changes the reference to which this components belongs.
* @param another the new reference this component should belong
*/
public synchronized final void setReference (Ref another) {
if (another == EMPTY) {
throw new IllegalArgumentException(
NbBundle.getBundle(CloneableTopComponent.class).getString("EXC_CannotAssign")
);
}
if (ref != null) {
// we belong to a reference
synchronized (ref) {
ref.getTable ().remove (this);
}
}
// register with the new reference this changes the ref field
another.register (this);
}
/** Called when this component is about to close.
* The default implementation just unregisters the clone from its clone list.
* <p>If this is the last component in its clone group, then
* {@link #closeLast} is called to clean up.
*
* @return <CODE>true</CODE> if there are still clone sisters left, or this was the last in its group
* but {@link #closeLast} returned <code>true</code>
*/
public boolean canClose (Workspace workspace, boolean last) {
if (last) {
return getReference ().unregister (this);
}
return true;
}
/** Called when the last component in a clone group is closing.
* The default implementation just returns <code>true</code>.
* Subclasses may specify some hooks to run.
* @return <CODE>true</CODE> if the component is ready to be
* closed, <CODE>false</CODE> to cancel
*/
protected boolean closeLast () {
return true;
}
public void readExternal (java.io.ObjectInput oi)
throws java.io.IOException, java.lang.ClassNotFoundException {
super.readExternal (oi);
if (serialVersion != 0) {
// since serialVersion > 0
// the reference object is also stored
Ref ref = (Ref)oi.readObject ();
if (ref != null) {
setReference (ref);
}
}
}
public void writeExternal (java.io.ObjectOutput oo)
throws java.io.IOException {
super.writeExternal (oo);
oo.writeObject (ref);
}
// say what? --jglick
/* Empty set that should save work with testing like
* <pre>
* if (ref == null || ref.isEmpty ()) {
* CloneableTopComponent c = new CloneableTopComponent (obj);
* ref = c.getReference ();
* }
* </pre>
* Instead one can always set <CODE>ref = Ref.EMPTY</CODE> and test only if
* <CODE>ref.isEmpty</CODE> returns <CODE>true</CODE>.
*/
/** Empty clone-sister list.
*/
public static final Ref EMPTY = new Ref ();
/** Keeps track of a group of sister clones.
* <P>
* <B>Warning:</B>
* For proper use
* subclasses should have method readResolve () and implement it
* in right way to deal with separate serialization of TopComponent.
*/
public static class Ref implements java.io.Serializable {
/** generated Serialized Version UID */
static final long serialVersionUID = 5543148876020730556L;
/** list of registered components */
private transient HashSet table;
/** Default constructor for creating empty reference.
*/
protected Ref () {
}
/** Constructor.
* @param c the component to refer to
*/
Ref (CloneableTopComponent c) {
getTable ().add (c);
}
/** Lazy getter for table list. Should be called when synchronized.
*/
private HashSet getTable () {
if (table == null) {
table = new HashSet (7);
}
return table;
}
/** Get all registered components.
* @return set of {@link CloneableTopComponent}s
*/
synchronized Set componentSet () {
return (Set)getTable ().clone ();
}
/** Enumeration of all registered components.
* @return enumeration of CloneableTopComponent
*/
public Enumeration getComponents () {
return java.util.Collections.enumeration (componentSet ());
}
/** Test whether there is any component in this set.
* @return <CODE>true</CODE> if the reference set is empty
*/
public synchronized boolean isEmpty () {
return getTable ().isEmpty ();
}
/** Retrieve an arbitrary component from the set.
* @return some component from the list of registered ones
* @exception NoSuchElementException if the set is empty
*/
public synchronized CloneableTopComponent getAnyComponent () {
return (CloneableTopComponent)getTable ().iterator ().next ();
}
/** Register new component.
* @param c the component to register
*/
final synchronized void register (CloneableTopComponent c) {
getTable ().add (c);
c.ref = this;
}
/** Unregister the component. If this is the last asks if it is
* allowed to unregister it.
*
* @param c the component to unregister
* @return true if the component agreed to be unregister
*/
final boolean unregister (CloneableTopComponent c) {
HashSet table = getTable ();
if ((table.size () > 1) || (c.closeLast())) {
synchronized (this) {
// has to be synchronized because access to table structure
table.remove(c);
}
return true;
} else {
return false;
}
}
} // end of Ref
}
/*
* Log
* 14 Gandalf 1.13 1/13/00 David Simonek i18n
* 13 Gandalf 1.12 12/10/99 Jaroslav Tulach canClose
* 12 Gandalf 1.11 12/8/99 Jaroslav Tulach TopComponent enhanced.
* 11 Gandalf 1.10 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 10 Gandalf 1.9 8/19/99 Ian Formanek Fixed bug 3520 -
* Breakpoint cannot be set in Editing wkspc when editor was closed in
* Debugging wkspc.
* 9 Gandalf 1.8 7/28/99 David Simonek canClose() parameters
* changed
* 8 Gandalf 1.7 7/11/99 David Simonek window system change...
* 7 Gandalf 1.6 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 6 Gandalf 1.5 3/29/99 Jesse Glick [JavaDoc]
* 5 Gandalf 1.4 1/17/99 Jaroslav Tulach closeLast that returns
* true if followed by a call to component.close ()
* 4 Gandalf 1.3 1/7/99 Jaroslav Tulach
* 3 Gandalf 1.2 1/7/99 Ian Formanek closeLast changed from
* abstract to return true by default
* 2 Gandalf 1.1 1/6/99 Jaroslav Tulach Change of package of
* DataObject
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
* Beta Change History:
* 0 Tuborg 0.14 --/--/98 Jaroslav Tulach synchronization methods refered to table
* 0 Tuborg 0.14 --/--/98 Jaroslav Tulach Deadlock warning: Do not synchronize against the component!!!!
* 0 Tuborg 0.15 --/--/98 Jaroslav Tulach Listeners added
* 0 Tuborg 0.16 --/--/98 Jan Formanek serialization fix
* 0 Tuborg 0.17 --/--/98 Petr Hamernik serialization fix 2
*/